package com.bumptech.glide.provider;

import android.support.annotation.Nullable;
import android.support.v4.util.ArrayMap;

import com.bumptech.glide.load.ResourceDecoder;
import com.bumptech.glide.load.engine.DecodePath;
import com.bumptech.glide.load.engine.LoadPath;
import com.bumptech.glide.load.resource.transcode.UnitTranscoder;
import com.bumptech.glide.util.MultiClassKey;

import java.util.Collections;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Maintains a cache of data, resource, and transcode classes to available
 * {@link LoadPath}s capable of decoding with the requested types.
 * <p>
 * 维持一个数据/资源的缓存，并且将类转换成能够用被请求类型解码的{@link LoadPath}
 */
public class LoadPathCache {
    private static final LoadPath<?, ?, ?> NO_PATHS_SIGNAL =
            new LoadPath<>(
                    Object.class,
                    Object.class,
                    Object.class,
                    Collections.singletonList(
                            new DecodePath<>(
                                    Object.class,
                                    Object.class,
                                    Object.class,
                                    Collections.<ResourceDecoder<Object, Object>>emptyList(),
                                    new UnitTranscoder<>(),
                                    /*listPool=*/ null)),
                    /*listPool=*/ null);

    private final ArrayMap<MultiClassKey, LoadPath<?, ?, ?>> cache = new ArrayMap<>();
    private final AtomicReference<MultiClassKey> keyRef = new AtomicReference<>();

    /**
     * Returns {@code} true if the given {@link LoadPath} is the signal object returned from
     * {@link #get(Class, Class, Class)} that indicates that we've previously found that there are
     * no available paths to load the requested resources and {@code false} otherwise.
     */
    public boolean isEmptyLoadPath(@Nullable LoadPath<?, ?, ?> path) {
        return NO_PATHS_SIGNAL.equals(path);
    }

    /**
     * May return {@link #NO_PATHS_SIGNAL} to indicate that we've previously found that there are 0
     * available load paths for the requested types. Callers must check using
     * {@link #isEmptyLoadPath(LoadPath)} before using any load path returned by this method.
     */
    @SuppressWarnings("unchecked")
    @Nullable
    public <Data, TResource, Transcode> LoadPath<Data, TResource, Transcode> get(
            Class<Data> dataClass, Class<TResource> resourceClass, Class<Transcode> transcodeClass) {
        MultiClassKey key = getKey(dataClass, resourceClass, transcodeClass);
        LoadPath<?, ?, ?> result;
        synchronized (cache) {
            result = cache.get(key);
        }
        keyRef.set(key);

        return (LoadPath<Data, TResource, Transcode>) result;
    }

    public void put(
            Class<?> dataClass, Class<?> resourceClass,
            Class<?> transcodeClass,
            @Nullable LoadPath<?, ?, ?> loadPath) {
        synchronized (cache) {
            cache.put(
                    new MultiClassKey(dataClass, resourceClass, transcodeClass),
                    loadPath != null ? loadPath : NO_PATHS_SIGNAL);
        }
    }

    private MultiClassKey getKey(Class<?> dataClass, Class<?> resourceClass,
                                 Class<?> transcodeClass) {
        MultiClassKey key = keyRef.getAndSet(null);
        if (key == null) {
            key = new MultiClassKey();
        }
        key.set(dataClass, resourceClass, transcodeClass);
        return key;
    }
}
